import Module from "./Module"
import SymbolList from "../SymbolList"
import Vectors from "../three/Vectors"
import CommonControlsModule from "./CommonControlsModule"
import { ScrollEvent } from "../event/ScrollEvent"
import { DragBeginEvent, DragEndEvent, DragEvent } from "../event/DragEvent"
import { KeyEvent } from "../event/KeyEvent"
import { BeginSwitchViewmodeMessage } from "../message/viewmode.message"
import ViewmodeData from "../data/ViewmodeData"
import browserUtil from "../utils/browser.util"
import y from "../math/7170"
import b from "../math/19674"
import * as THREE from "three"
import E from "../math/63560"
import RawPointersEvent from "../event/RawPointersEvent"
import MeshData from "../data/MeshData"
import RaycasterModule from "./RaycasterModule"
import viewmodeEnum from "../enum/viewmode.enum"
const D = 1000 / 60,
  C = 80 * y.Ue,
  P = 0 * y.Ue,
  I = Math.PI / 1000,
  N = 0.00001

import { SetDollhouseVerticalLimits } from "../command/house.command"
import funcUtil from "../utils/func.util"
import hotKeyEnum, { KeyState } from "../enum/hotKey.enum"
import EngineContext from "../EngineContext"
import InputIniModule from "./InputIniModule"
import CameraTouchControls from "../controls/CameraTouchControls"
import DollhouseCameraControls from "../controls/DollhouseCameraControls"
var R = {
  0: "NONE",
  1: "ROTATE",
  2: "ZOOM",
  4: "PAN",
  NONE: 0,
  PAN: 4,
  ROTATE: 1,
  ZOOM: 2
}

export default class DollhouseControlsModule extends Module {
  controlState: number
  movementKeys: THREE.Vector2
  adjustedBounds: THREE.Box3
  didDrag: boolean
  resetControlState: () => void
  convertDeltaToLocal: (i: any) => THREE.Vector3
  cameraPoseProxy: any
  cameraCheck: (o: any, a: any, l: any) => boolean
  meshData: MeshData
  viewmodeData: ViewmodeData
  commonControlsModule: CommonControlsModule
  controls: any
  modelSize: number
  cameraTouchControls: CameraTouchControls
  constructor() {
    super(...arguments), (this.name = "dollhouse-controls")
    this.controlState = R.NONE
    this.movementKeys = new THREE.Vector2()
    this.adjustedBounds = new THREE.Box3()
    this.didDrag = !1
    this.resetControlState = () => {
      this.controlState = R.NONE
    }
    this.convertDeltaToLocal = (() => {
      const e = new THREE.Vector3(),
        t = new THREE.Vector3()
      return i => {
        const n = i.x || 0,
          s = i.y || 0,
          o = 2 * n * this.cameraPoseProxy.pose.focalDistance,
          a = (2 * s * this.cameraPoseProxy.pose.focalDistance) / this.cameraPoseProxy.pose.aspect()
        return (
          e.copy(Vectors.RIGHT).applyQuaternion(this.cameraPoseProxy.pose.rotation).setY(0).setLength(o),
          t.copy(Vectors.FORWARD).applyQuaternion(this.cameraPoseProxy.pose.rotation),
          e.add(t.setY(0).setLength(a)),
          e
        )
      }
    })()
    this.cameraCheck = (() => {
      function e(e) {
        return e.x >= 1 || e.x <= -1 || e.y >= 1 || e.y <= -1 || e.z <= -1 || e.z >= 1
      }
      const t = []
      for (let e = 0; e < 8; e++) t.push(new THREE.Vector3())
      const i = new THREE.Matrix4(),
        n = new THREE.Matrix4(),
        s = new THREE.Box3()
      return (o, a, l) => {
        if (l < 2) return !1
        if (!b.cb(o, a, this.cameraPoseProxy.pose.projection, this.adjustedBounds)) return !1
        b.jB(this.meshData.bounds, t)
        i.compose(o, a, Vectors.UNIT)
        i.invert()
        n.copy(this.cameraPoseProxy.pose.projection.asThreeMatrix4())
        s.makeEmpty()
        let c = !1
        for (let e = 0; e < 8; e++) t[e].applyMatrix4(i), (t[e].z = funcUtil.getMax(t[e].z, -E.oR.far, -E.oR.near)), t[e].applyMatrix4(n), s.expandByPoint(t[e])
        ;(c = e(s.min) || e(s.max)), s.min.clampScalar(-1, 1), s.max.clampScalar(-1, 1)
        return (s.max.x - s.min.x) * (s.max.y - s.min.y) > (c ? 0.5 : 0.1)
      }
    })()
  }
  async init(e, t: EngineContext) {
    this.cameraPoseProxy = e.cameraPoseProxy
    ;[this.viewmodeData, this.meshData, this.commonControlsModule] = await Promise.all([
      t.market.waitForData(ViewmodeData),
      t.market.waitForData(MeshData),
      t.getModule(CommonControlsModule)
    ])
    this.bindings.push(
      t.subscribe(BeginSwitchViewmodeMessage, e => {
        this.controls.stop()
        this.resetControlState()
      })
    )
    t.commandBinder.addBinding(SetDollhouseVerticalLimits, async e =>
      this.controls.setPhiLimits(
        void 0 !== e.phiLowerLimitDegrees ? e.phiLowerLimitDegrees * y.Ue : P,
        void 0 !== e.phiUpperLimitDegrees ? e.phiUpperLimitDegrees * y.Ue : C,
        this.validateViewmode()
      )
    )
    const i = await t.getModuleBySymbol<InputIniModule>(SymbolList.INPUT)
    this.modelSize = Math.max(this.meshData.size.length(), 1)
    this.setupBounds()
    this.controls = new DollhouseCameraControls(this.cameraPoseProxy, this.cameraCheck, this.meshData.bounds, !0)
    this.commonControlsModule.addControls(viewmodeEnum.Dollhouse, this.controls)
    const n = await t.getModule(RaycasterModule)
    this.cameraTouchControls = new CameraTouchControls(this.cameraPoseProxy, this.cameraCheck, n)
    this.bindings.push(
      i.registerHandler(ScrollEvent, e => {
        this.validateViewmode() && this.onScrollWheel(e)
      })
    )
    this.bindings.push(
      i.registerHandler(DragBeginEvent, e => {
        this.validateViewmode() && ((this.didDrag = !1), this.onDragBegin(e.buttons))
      })
    )
    this.bindings.push(
      i.registerHandler(DragEndEvent, e => {
        this.validateViewmode() &&
          (e.timeSinceLastMove < 100 && this.didDrag && (this.onDrag(e.delta), this.controls.update(D), this.controls.stopAcceleration()),
          this.onDragEnd(e.delta, e.buttons))
      })
    )
    this.bindings.push(
      i.registerHandler(DragEvent, e => {
        this.validateViewmode() && ((this.didDrag = !0), this.onDrag(e.delta), this.controls.update(D), this.controls.stop())
      })
    )
    this.bindings.push(
      i.registerHandler(RawPointersEvent, e => {
        this.validateViewmode() && this.cameraTouchControls.rawPointerUpdate(e)
      })
    )
    this.bindings.push(
      i.registerHandler(KeyEvent, e => {
        this.validateViewmode() && e.state !== KeyState.PRESSED && this.onKey(e.key, e.state)
      })
    )
    this.cameraPoseProxy.newSession(this)
  }
  onUpdate(e) {
    this.validateViewmode() && this.controls.update(e)
  }
  onAccessGranted(e) {
    this.controls.onAccessGranted(e)
    this.cameraTouchControls.onAccessGranted(e)
  }
  onAccessRevoked(e) {
    this.controls.onAccessRevoked(e)
    this.cameraTouchControls.onAccessRevoked(e)
  }
  onScrollWheel(e) {
    if (0 !== e.delta.y) {
      const t = (1 / this.cameraPoseProxy.pose.focalDistance) * 2
      const i = this.modelSize
      this.controls.setZoomAcceleration((0.1 * e.delta.y) / (t * i * 500))
      this.controls.update(D), this.controls.setZoomAcceleration(0)
    }
  }
  onDragBegin(e) {
    if (this.controlState === R.NONE) {
      const t = e
      this.controlState = t
    }
    this.controls.stop()
  }
  onDrag(e) {
    switch (this.controlState) {
      case R.ROTATE:
        this.controls.setOrbitalAcceleration({
          x: -Math.PI * e.x,
          y: -Math.PI * e.y
        })
        break
      case R.PAN:
        const t = this.convertDeltaToLocal(e)
        this.controls.setPanAcceleration({ x: -t.x, y: -t.z })
        break
      case R.ZOOM:
        0 !== e.y && this.controls.setZoomAcceleration(-e.y)
    }
  }
  onDragEnd(e, t) {
    t & this.controlState || ((this.controlState = R.NONE), browserUtil.isMobile() && this.controls.stop())
  }
  onKey(e: number, t: number) {
    const i = t === KeyState.DOWN
    let n = !1
    switch (e) {
      case hotKeyEnum.LEFTARROW:
      case hotKeyEnum.J:
        this.controls.setOrbitalAcceleration({ x: i ? I : 0 }, !0)
        break
      case hotKeyEnum.RIGHTARROW:
      case hotKeyEnum.L:
        this.controls.setOrbitalAcceleration({ x: i ? -I : 0 }, !0)
        break
      case hotKeyEnum.UPARROW:
      case hotKeyEnum.I:
        this.controls.setOrbitalAcceleration({ y: i ? -I : 0 }, !0)
        break
      case hotKeyEnum.DOWNARROW:
      case hotKeyEnum.K:
        this.controls.setOrbitalAcceleration({ y: i ? I : 0 }, !0)
        break
      case hotKeyEnum.W:
        ;(this.movementKeys.y = i ? 1 : 0), (n = !0)
        break
      case hotKeyEnum.S:
        ;(this.movementKeys.y = i ? -1 : 0), (n = !0)
        break
      case hotKeyEnum.D:
        ;(this.movementKeys.x = i ? 1 : 0), (n = !0)
        break
      case hotKeyEnum.A:
        ;(this.movementKeys.x = i ? -1 : 0), (n = !0)
    }
    if (n) {
      const e = this.convertDeltaToLocal(this.movementKeys).setLength(0.02)
      this.controls.setPanAcceleration({ x: e.x, y: e.z })
    }
  }
  validateViewmode() {
    return this.viewmodeData.currentMode === viewmodeEnum.Dollhouse
  }
  setupBounds() {
    function e(e, t) {
      return Math.sign(e + t) === Math.sign(e) ? e + t : 0
    }
    this.adjustedBounds.copy(this.meshData.bounds)
    this.adjustedBounds.min.x = e(this.adjustedBounds.min.x, 4)
    this.adjustedBounds.min.y = e(this.adjustedBounds.min.y, 4)
    this.adjustedBounds.min.z = e(this.adjustedBounds.min.z, 4)
    this.adjustedBounds.max.x = e(this.adjustedBounds.max.x, -4)
    this.adjustedBounds.max.y = e(this.adjustedBounds.max.y, -4)
    this.adjustedBounds.max.z = e(this.adjustedBounds.max.z, -4)
    const t = new THREE.Vector3()
    this.adjustedBounds.getSize(t)
    this.adjustedBounds.setFromCenterAndSize(this.meshData.center, t)
  }
}
